Wilson@思源

目 录

js 代码片段实现,只聚焦当前文档所在的目录树

需求

在使用“始终定位打开的文档”功能时,只展开当前文档的文档树,自动关闭(不展开)其它无关的文档树。
例如:
有笔记本 A,内有文档 A1,子文档 A2、子子文档 A3。
有笔记本 B,内有文档 B1,子文档 B2、子子文档 B3。
已将文档 A2、B2 打开,显示在上方页签栏中。
启用“始终定位打开的文档”功能。
点击 A2 页签,左侧文档树自动展开,并定位到 A-A1-A2;A3 列表不展开
点击 B2 页签,左侧文档树自动展开,定位到 B-B1-B2;同时关闭 A-A1-A2 文档树的展开状态,恢复到笔记本 A。也就是仅保留当前活动页签对应的文档树是展开状态,其它文档树分支全部关闭,包括当前活动文档的子文档树。

实现

js
// 加载时是否自动定位当前文档 const autoFocusTreeOnload = true; // 等待标签页容器渲染完成后开始监听 whenElementExist('.layout__center').then(async element => { // 等待笔记列表加载完毕 await sleep(40); // 监听页签切换事件 observeTabChanged(element, (tab) => { // 折叠所有笔记,然后定位当前笔记 collapseAllBooksThenFocusCurrentBook(element, tab); }); // 加载时定位当前文档 if(autoFocusTreeOnload) { await sleep(40); focusCurrentDocInTrees(); } }); // 折叠所有笔记,然后定位当前笔记 async function collapseAllBooksThenFocusCurrentBook(element, tab) { let currNodeId = ""; // 等待激活文档加载完毕 await whenElementExist(()=>{ const content = element.querySelector('.layout-tab-container [data-id="'+tab.getAttribute("data-id")+'"]'); // 获取当前文档的node-id currNodeId = document.querySelector('.layout-tab-container [data-id="'+tab.getAttribute("data-id")+'"] .protyle-title')?.getAttribute("data-node-id") || ""; return content && content.getAttribute("data-loading") === "finished" && currNodeId; }); // 获取当前文档所在的路径和当前笔记,并折叠当前文档以外的目录 let currBoxDataUrl = ""; if(currNodeId){ // 定位当前文档 focusCurrentDocInTrees(); // 等待定位完成 let currTreeItem = null; await whenElementExist(()=>{ currTreeItem = document.querySelector("ul.b3-list[data-url] li[data-node-id='" + currNodeId + "']"); return currTreeItem; }); // 获取当前文档在目录树中的node-id if(currTreeItem) { // 获取当前笔记的data-url currBoxDataUrl = currTreeItem.closest('ul.b3-list[data-url]')?.getAttribute("data-url") || ""; if(currBoxDataUrl){ // 获取当前文档所在的路径 currTreeItemPath = currTreeItem.getAttribute("data-path"); // 获取所有展开的目录 const trees = document.querySelectorAll("ul.b3-list[data-url='" + currBoxDataUrl + "'] span.b3-list-item__toggle svg.b3-list-item__arrow--open"); trees.forEach(arrowBtn => { // 如果是当前文档的上级目录则跳过 const itemPath = arrowBtn.closest('li.b3-list-item')?.getAttribute("data-path")?.replace(/\.sy$/i, ''); if(currTreeItemPath.startsWith(itemPath)){ return; } // 其他目录则折叠 if (arrowBtn.parentElement) { arrowBtn.parentElement.click(); } }); } } } // 折叠除当前笔记外的所有笔记 document.querySelectorAll("ul.b3-list[data-url]").forEach(async book => { // 如果是当前笔记则跳过 if(book.getAttribute("data-url") === currBoxDataUrl) { return; } // 折叠笔记 const bookArrowBtn = book.querySelector('li[data-type="navigation-root"] span.b3-list-item__toggle'); if (bookArrowBtn && bookArrowBtn.firstElementChild.classList.contains("b3-list-item__arrow--open")) { bookArrowBtn.click(); } }); } // 在目录树中定位当前文档 async function focusCurrentDocInTrees() { // 定位当前文档 document.querySelector(".layout-tab-container .block__icons span[data-type=focus]")?.click(); // 等待定位完成 await whenElementExist(()=>{ return document.querySelector('ul.b3-list[data-url] li[data-type="navigation-file"].b3-list-item--focus'); }); // 处理官方定位,在未打开目录树时,左侧dock区目录树显示隐藏按钮样式会获取焦点的bug await sleep(40); const dockFileTreeBtn = document.querySelector('#dockLeft span[data-type="file"]'); if(document.querySelector('#layouts .layout__dockl')?.style?.width === "0px"){ if(dockFileTreeBtn.classList.contains("dock__item--active")) { dockFileTreeBtn.classList.remove("dock__item--active"); } if(dockFileTreeBtn.classList.contains("dock__item--activefocus")) { dockFileTreeBtn.classList.remove("dock__item--activefocus"); } } } // 监听页签切换事件 function observeTabChanged(parentNode, callback) { // 创建一个回调函数来处理观察到的变化 const observerCallback = function(mutationsList, observer) { // 用常规方式遍历 mutationsList 中的每一个 mutation for (let mutation of mutationsList) { // 属性被修改 if (mutation.type === 'attributes' && mutation.attributeName === 'class') { const element = mutation.target; if (element.tagName.toLowerCase() === 'li' && element.getAttribute('data-type') === 'tab-header' && element.classList.contains('item--focus')) { if(typeof callback === 'function') callback(element); } } // 如果有新的子节点被添加 if (mutation.type === 'childList') { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'li') { if (node.getAttribute('data-type') === 'tab-header' && node.classList.contains('item--focus')) { if(typeof callback === 'function') callback(node); } } }); } } }; // 创建一个观察器实例并传入回调函数 const observer = new MutationObserver(observerCallback); // 配置观察器:传递一个对象来指定观察器的行为 const config = { attributes: true, attributeFilter: ['class'], childList: true, subtree: true }; // 开始观察目标节点 observer.observe(parentNode, config); // 返回一个函数,用于停止观察 return function stopObserving() { observer.disconnect(); }; } // 延迟执行 function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // 等待元素渲染完成后执行 function whenElementExist(selector) { return new Promise(resolve => { const checkForElement = () => { let element = null; if (typeof selector === 'function') { element = selector(); } else { element = document.querySelector(selector); } if (element) { resolve(element); } else { requestAnimationFrame(checkForElement); } }; checkForElement(); }); }

效果

使用方法

设置 》外观 》代码片段 》js 中新增加代码片段,然后把上面的代码粘贴过去即可。

最后

由于刚接触思源,对思源 api 还不是很了解,所以选择用 js 代码片段实现,纯原生 js 实现,方法比较笨,勉强能用。
不足之处也请各界大佬们帮忙改进或多多批评指正!
文章来源:https://ld246.com/article/1717306355600